package QC.MessageBox.handler;

import QC.MessageBox.annotation.MethodLog;
import QC.MessageBox.annotation.RouteMapping;
import QC.MessageBox.beans.*;
import QC.MessageBox.beans.dto.PageInfo;
import QC.MessageBox.beans.dto.RecentContactFans;
import QC.MessageBox.common.*;
import QC.MessageBox.enums.*;
import QC.MessageBox.service.*;
import io.vertx.core.CompositeFuture;
import io.vertx.core.Future;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.http.HttpServerResponse;
import io.vertx.core.json.Json;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.RoutingContext;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * Created by Andaren on 2017/5/9.
 * Used for: 处理App后台请求
 */
@RouteMapping(path = "/api/app")
public class AppHandler extends HandlerBase {

    private static final Logger LOGGER = LoggerFactory.getLogger(AppHandler.class);

    public AppHandler () {}

    public AppHandler (Map<String, Object> handlerMap) {
        Objects.requireNonNull(handlerMap);
        this.dataAccess = (DataAccess)handlerMap.get("dataAccess");
        this.httpService = (HttpService)handlerMap.get("httpService");
        this.sqlService = (MySQLService)handlerMap.get("mySQLService");
        this.redisService = (RedisService)handlerMap.get("redisService");
//        this.mongoService = (MongoService) handlerMap.get("mongoService");
        this.webClientService = (WebClientService)handlerMap.get("webClientService");
//        this.blockingService = (BlockingService)handlerMap.get("blockingService");
    }

    /**
     * 新增公众号信息
     *
     * @param context
     */
    @RouteMapping(path = "/public", method = HttpMethod.POST)
    @MethodLog("新增加公众号信息")
    public void saveNewPublic (RoutingContext context) {
        HttpServerRequest request = context.request();
        HttpServerResponse response = context.response();

        JsonObject publicJson = context.getBodyAsJson();
        Objects.requireNonNull(publicJson);
        WXPublic newPublic = new WXPublic();
        newPublic.setAppId(publicJson.getString("appId"))
                .setSecret(publicJson.getString("secret"))
                .setDeveloperId(publicJson.getString("developerId"))
                .setName(publicJson.getString("name"))
                .setCreateTime(PeriodicTime.getCurrentTimeByyyyMMddHHmmss())
                .setChannelType(publicJson.getInteger("channelType"));
        if (StringUtils.isNotBlank(publicJson.getString("accessToken"))) {
            newPublic.setAccessToken(publicJson.getString("accessToken"));
        }
        LOGGER.info("app后台调用[新增公众号信息]->{" + publicJson + "}");
        Future<Void> tokenFut = Future.future();
        Future<String> reqTokenFut = Future.future();
        if (0 == publicJson.getInteger("channelType")) {
            reqTokenFut =  webClientService.getAccessTokenByTime(Constants.GRANT_TYPE, newPublic.getAppId(), newPublic.getSecret());
        } else {
            reqTokenFut.complete(publicJson.getString("accessToken"));
        }
        reqTokenFut.setHandler(tokenAs -> {
                    if (tokenAs.failed()) {
                        // 日常返回
                        response
                                .putHeader("content-type", "text/json")
                                .end(Constants.failedResult(tokenAs.cause().getMessage()));
                        tokenFut.fail("公众号信息不正确");
                    } else {
                        // 日常返回
                        response
                                .putHeader("content-type", "text/json")
                                .end(Constants.successResult());

                        newPublic.setAccessToken(tokenAs.result());
                        // test-------------------------------------------------------------
                        saveAccessTokenRequestByTest(tokenAs.result());
                        //------------------------------------------------------------------
                        // 保存公众号
                        dataAccess.savePublic(newPublic)
                                .compose(pub -> {
                                    Future saveFut = Future.future();
                                    if (ChannelType.WX.val() == pub.getChannelType()) {
                                        // 微信公众号
                                        saveFut = generateHistoryFansByDeveloperId(pub.getDeveloperId());
                                    } else if (ChannelType.WB.val() == pub.getChannelType()) {
                                        // 微博公众平台
                                        saveFut = pullFansFromWB(pub);
                                    }
                                    return saveFut;
                                })
                                .compose((Void) -> {
                                    tokenFut.complete();
                                }, tokenFut);

                    }
                });
        tokenFut.setHandler(as -> {
            if (as.failed()) {
                LOGGER.info("新增公众号失败：" + as.cause().getMessage());
                // 日常返回
                response
                        .putHeader("content-type", "text/json")
                        .end(Constants.failedResult(as.cause().getMessage()));
            } else {
                LOGGER.info("[新增公众号成功]");
            }
        });
    }

    /**
     *  更新公众号信息
     *
     * @param context
     */
    @RouteMapping(path = "/public/update", method = HttpMethod.POST)
    @MethodLog("更新公众号信息")
    public void updatePublicInfo(RoutingContext context) {
        HttpServerRequest request = context.request();
        HttpServerResponse response = context.response();

        JsonObject publicJson = context.getBodyAsJson();
        String developerId = publicJson.getString("developerId");
        LOGGER.info("app后台调用[更新公众号信息]->{" + publicJson + "}");

        dataAccess.getWXPublicInfoByDeveloperId(developerId)
                .compose(publicInfo -> {
                    Future<WXPublic> publicInfoFut = Future.future();
                    publicInfo.setAppId(publicJson.getString("appId"))
                            .setSecret(publicJson.getString("secret"))
                            .setName(publicJson.getString("name"));
                    // 尝试获取Token
                    webClientService.getAccessTokenByTime(Constants.GRANT_TYPE, publicInfo.getAppId(), publicInfo.getSecret())
                            .setHandler(tokenAs -> {
                                if (tokenAs.failed()) {
                                    // 日常返回
                                    response
                                            .putHeader("content-type", "text/json")
                                            .end(Constants.failedResult(tokenAs.cause().getMessage()));
                                    publicInfoFut.fail("公众号信息不正确");
                                } else {
                                    // 日常返回
                                    response
                                            .putHeader("content-type", "text/json")
                                            .end(Constants.successResult());
                                    // test-------------------------------------------------------------
                                    saveAccessTokenRequestByTest(tokenAs.result());
                                    //------------------------------------------------------------------
                                    // 更新公众号信息
                                    publicInfo.setAccessToken(tokenAs.result());
                                    dataAccess.updatePublic(publicInfo).setHandler(updateAs -> {
                                        if (updateAs.failed()) {
                                            publicInfoFut.fail(updateAs.cause());
                                        } else {
                                            publicInfoFut.complete(publicInfo);
                                        }
                                    });
                                }
                            });
                    return publicInfoFut;
                }).setHandler(publicAs -> {
            if (publicAs.failed()) {
                LOGGER.info("更新公众号异常：" + publicAs.cause().getMessage());
                // 日常返回
                response
                        .putHeader("content-type", "text/json")
                        .end(Constants.successResult());
            } else {
                LOGGER.info("[更新公众号成功]");
            }
        });
    }

    /**
     * 客服转接时，更新客服映射关系
     * @param context
     */
    @RouteMapping(path = "/cs/transfer", method = HttpMethod.POST)
    @MethodLog("客服转接（转接公众号下的所有客户）")
    public void transferCustomerServerByCs(RoutingContext context) {
        HttpServerRequest request = context.request();
        HttpServerResponse response = context.response();
        response
                .putHeader("content-type", "text/json")
                .end(Constants.successResult());

        JsonObject csJson = context.getBodyAsJson();
        Objects.requireNonNull(csJson);
        String customerServerId = csJson.getString("csId");
        String newUserId = csJson.getString("newUserId");
        String newCsId = csJson.getString("newCsId");
        String nickname = csJson.getString("nickname");
        String developerId = csJson.getString("developerId");
        AppUser newCs = new AppUser();
        newCs.setCsId(newCsId)
                .setTalkId(newUserId)
                .setNickname(nickname);
        LOGGER.info("app后台调用[客服转接时，更新客服映射关系]->{" + csJson + "}");
        dataAccess.transferCsTalkId(customerServerId, newCs, developerId).setHandler(as -> {
            if (as.failed()) {
                LOGGER.info("客服转接处理失败");
            } else {
                LOGGER.info("客服转接处理完成");
            }
        });
    }

    /**
     * 转接指定列表中的粉丝
     * @param context
     */
    @RouteMapping(path = "/cs/transfer/list", method = HttpMethod.POST)
    @MethodLog("客服转接（转接指定列表中的客户）")
    public void transferCustomerServerInList(RoutingContext context) {
        HttpServerRequest request = context.request();
        HttpServerResponse response = context.response();
        response
                .putHeader("content-type", "text/json")
                .end(Constants.successResult());

        JsonObject csJson = context.getBodyAsJson();
        Objects.requireNonNull(csJson);
        JsonArray openIdArray = csJson.getJsonArray("openIds");
        String newUserId = csJson.getString("newUserId");
        String newCsId = csJson.getString("newCsId");
        String nickname = csJson.getString("nickname");
        String developerId = csJson.getString("developerId");
        AppUser newCs = new AppUser();
        newCs.setCsId(newCsId)
                .setTalkId(newUserId)
                .setNickname(nickname);
        LOGGER.info("app后台调用[客服转接时，更新客服映射关系]->{" + csJson + "}");
        dataAccess.transferCsTalkIdByList(openIdArray, newCs, developerId).setHandler(as -> {
            if (as.failed()) {
                LOGGER.info("客服转接处理失败");
            } else {
                LOGGER.info("客服转接处理完成");
            }
        });
    }

    /**
     * 获取分页的聊天记录
     * @param context
     */
    @RouteMapping(path = "/recored/:openid/:pagenum/:size")
    @MethodLog("获取最近一个月内的聊天记录")
    public void getMsgRecordByPage(RoutingContext context) {
        HttpServerRequest request = context.request();
        HttpServerResponse response = context.response();

        String openId = request.getParam("openid");
        String pageNumStr = request.getParam("pagenum");
        String pageSizeStr = request.getParam("size");
        Objects.requireNonNull(pageNumStr);
        Objects.requireNonNull(pageSizeStr);
        Objects.requireNonNull(openId);
        Integer pageNum = Integer.valueOf(pageNumStr);
        Integer pageSize = Integer.valueOf(pageSizeStr);
        Integer pageStartIndex = (Integer.valueOf(pageNum) - 1) * pageSize;

        Future<Void> pagedMsgRecordfuture = Future.future();
        sqlService.countAllMsgRecordByOpenid(openId)
                .compose(count -> {
                    Future<String> msgRecordsFut = Future.future();
                    JsonObject returnJson = new JsonObject();
                   if (count == 0) {
                       returnJson
                               .put("records", new JsonArray())
                               .put("count", "0");
                       msgRecordsFut.complete(returnJson.encodePrettily());
                   } else {
                       sqlService.getMsgRecordsOrderByDateInPage(openId, pageStartIndex, pageSize)
                               .compose(recordList -> {
                                   if (recordList.size() > 0) {
                                       JsonArray recordArray = new JsonArray();
                                       for(MsgRecord record : recordList) {
                                           // 消息内容emoji解码
                                           record.decodeContent();
                                           JsonObject r = JsonObject.mapFrom(record);
                                           r.put("content", record.contentByType());
                                           r.remove("id");
                                           r.remove("isRead");
                                           recordArray.add(r);
                                       }
                                       returnJson.put("records", recordArray)
                                               .put("count", "" + count);
                                       msgRecordsFut.complete(returnJson.encodePrettily());
                                   } else {
                                       returnJson.put("records", new JsonArray())
                                               .put("count", "" + count);
                                       msgRecordsFut.complete(returnJson.encodePrettily());
                                   }
                               }, msgRecordsFut);
                   }
                   return msgRecordsFut;
                })
                .compose(returnJsonStr -> {
                    response

                            .putHeader("content-type", "text/json")
                            .end(returnJsonStr);
                    pagedMsgRecordfuture.complete();
                }, pagedMsgRecordfuture);
        pagedMsgRecordfuture.setHandler(as -> {
           if (as.failed()) {
               LOGGER.info("[获取分页的聊天记录]失败：{}", as.cause().getMessage());
               response
                       .putHeader("content-type", "text/json")
                       .end(
                               (new JsonObject()
                                       .put("records", new JsonArray())
                                       .put("count", "0"))
                                       .encodePrettily()
                       );
           } else {
               LOGGER.info("[获取分页的聊天记录]success");
           }
        });
    }

    /**
     * 获取用户轨迹
     * @param context
     */
    @RouteMapping(path = "/:developerid/:openid/track")
    @MethodLog("获取用户轨迹")
    public void getUserActions(RoutingContext context) {
        Future<Void> userActionFut = Future.future();

        HttpServerRequest request = context.request();
        HttpServerResponse response = context.response();
        String openId = request.getParam("openid");
        String developerId = request.getParam("developerid");
        Objects.requireNonNull(openId);
        Future<WXUser> wxUserFuture = dataAccess.findWxUserByOpenId(openId);
        Future<WXPublic> publicFuture = dataAccess.getWXPublicInfoByDeveloperId(developerId);
        Future<List<UserAction>> userActionListFuture = sqlService.getUserActionsByOpenId(openId);
        CompositeFuture.all(wxUserFuture, publicFuture, userActionListFuture)
                .compose(compositeFutureAs -> {
                    WXUser wxUser = compositeFutureAs.resultAt(0);
                    WXPublic wxPublic = compositeFutureAs.resultAt(1);
                    List<UserAction> userActions = compositeFutureAs.resultAt(2);
                    JsonObject returnJson = new JsonObject();
                    JsonArray userActionArray = new JsonArray();
                    returnJson.put("tracks:", userActionArray);
                    for (UserAction action : userActions) {
                        StringBuilder actSb = new StringBuilder();
                        actSb.append("公众号：[" + wxPublic + "]")
                                .append("的用户[" + wxUser.getWxNickName() + "]")
                                .append("在[" + action.getActStartTime() + "]")
                                .append(action.getDescription());
                        userActionArray.add(actSb.toString());

                    }
                    response
                            .putHeader("content-type", "text/json")
                            .end(returnJson.encodePrettily());
                    userActionFut.complete();

                }, userActionFut);

        userActionFut.setHandler(as -> {
           if (as.failed()) {
               LOGGER.info("[获取用户轨迹失败]{}", as.cause().getMessage());
           } else {
               LOGGER.info("[获取用户轨迹完成]");
           }
        });
    }

    /**
     * 获取公众号线所有在线用户
     * @param context
     */
    @RouteMapping(path = "/:developerid/onlinetime/:pagenum/:size")
    @MethodLog("获取公众号线所有在线用户")
    public void getOnlineUserInPublic(RoutingContext context) {
        HttpServerRequest request = context.request();
        HttpServerResponse response = context.response();

        String developerId = request.getParam("developerId");
        Integer pageNum = Integer.valueOf(request.getParam("pagenum"));
        Integer pageSize = Integer.valueOf(request.getParam("size"));
        Integer currentIndex = (pageNum - 1) * pageSize;

        Future<List<RecentContactFans>> recentFansFut = Future.future();
        // 获取公众号下在线粉丝
        sqlService.getOnlineFansByDeveloperId(null, developerId, currentIndex, pageSize).setHandler(recentFansFut);
        Future<Void> publicOnlineUserFuture = makeUserOnlineInfo(recentFansFut, response);
        publicOnlineUserFuture.setHandler(publicOnlineAr -> {
            if (publicOnlineAr.failed()) {
                LOGGER.info("[获取公众号线所有在线用户失败],error={}", publicOnlineAr.cause().getMessage());
            } else {
                LOGGER.info("[获取公众号线所有在线用户成功]");
            }
        });
    }

    /**
     * 获取客服下所有在线用户
     * @param context
     */
    @RouteMapping(path = "/:csid/online/:pagenum/:size")
    @MethodLog("获取客服下在线粉丝")
    public void getOnlineUserInCustomerServer(RoutingContext context) {
        HttpServerRequest request = context.request();
        HttpServerResponse response = context.response();

        String csId = request.getParam("csid");
        Integer pageNum = Integer.valueOf(request.getParam("pagenum"));
        Integer pageSize = Integer.valueOf(request.getParam("size"));
        Integer currentIndex = (pageNum -1) * pageSize;

        Future<List<RecentContactFans>> usrLimitFut = Future.future();
        // 获取客服下在线粉丝
        sqlService.getOnlineFansByDeveloperId(csId, null, currentIndex, pageSize)
                .setHandler(usrLimitFut.completer());
        Future<Void> csOnlineUserFuture = makeUserOnlineInfo(usrLimitFut, response);
        csOnlineUserFuture.setHandler(csOnlineAr -> {
            if (csOnlineAr.failed()) {
                LOGGER.info("[获取客服下所有在线用户失败],error={}", csOnlineAr.cause().getMessage());
            } else {
                LOGGER.info("[获取客服下所有在线用户成功]");
            }
        });
    }

    /**
     * 全渠道修改
     *      获取渠道下客服下的粉丝信息
     * @param context
     */
    @RouteMapping(path = "/:csid/:channelType/online/:pagenum/:size")
    @MethodLog("获取客服的渠道下在线粉丝")
    public void getOnlineUserByChannelAndCS(RoutingContext context) {
        HttpServerRequest request = context.request();
        HttpServerResponse response = context.response();

        String csId = request.getParam("csid");
        String channelType = request.getParam("channelType");
        Integer pageNum = Integer.valueOf(request.getParam("pagenum"));
        Integer pageSize = Integer.valueOf(request.getParam("size"));
        Integer currentIndex = (pageNum -1) * pageSize;

        Future<List<RecentContactFans>> usrLimitFut = Future.future();
        // 获取客服下在线粉丝
        sqlService.getChannelOnlineFansByCS(csId, Integer.valueOf(channelType), null, currentIndex, pageSize)
                .setHandler(usrLimitFut.completer());
        Future<Void> csOnlineUserFuture = makeUserOnlineInfo(usrLimitFut, response);
        csOnlineUserFuture.setHandler(csOnlineAr -> {
            if (csOnlineAr.failed()) {
                LOGGER.info("[获取客服下所有在线用户失败],error={}", csOnlineAr.cause().getMessage());
            } else {
                LOGGER.info("[获取客服下所有在线用户成功]");
            }
        });
    }

    /**
     * 全渠道修改
     *      获取渠道下客服下的粉丝信息
     * @param context
     */
    @RouteMapping(path = "/:csid/:channelType/:name/online/:pagenum/:size")
    @MethodLog("获取客服的渠道下在线粉丝")
    public void fuzzyQueryOnlineUserByChannelAndCS(RoutingContext context) {
        HttpServerRequest request = context.request();
        HttpServerResponse response = context.response();

        String csId = request.getParam("csid");
        String channelType = request.getParam("channelType");
        String name = request.getParam("name");
        Integer pageNum = Integer.valueOf(request.getParam("pagenum"));
        Integer pageSize = Integer.valueOf(request.getParam("size"));
        Integer currentIndex = (pageNum -1) * pageSize;

        Future<List<RecentContactFans>> usrLimitFut = Future.future();
        // 获取客服下在线粉丝
        sqlService.getChannelOnlineFansByCS(csId, Integer.valueOf(channelType), name, currentIndex, pageSize)
                .setHandler(usrLimitFut.completer());
        Future<Void> csOnlineUserFuture = makeUserOnlineInfo(usrLimitFut, response);
        csOnlineUserFuture.setHandler(csOnlineAr -> {
            if (csOnlineAr.failed()) {
                LOGGER.info("[获取客服下所有在线用户失败],error={}", csOnlineAr.cause().getMessage());
            } else {
                LOGGER.info("[获取客服下所有在线用户成功]");
            }
        });
    }

    /**
     * 全渠道修改
     *      获取渠道下客服渠道列表下的粉丝信息
     * @param context
     */
    @RouteMapping(path = "/cs/channel/fans", method = HttpMethod.POST)
    @MethodLog("获取客服渠道列表下在线粉丝")
    public void queryOnlineUserByCSChannelList(RoutingContext context) {
        HttpServerRequest request = context.request();
        HttpServerResponse response = context.response();

        JsonObject reqParams = context.getBodyAsJson();

        String name = reqParams.getString("name");
        Integer pageNum = reqParams.getInteger("pages");
        Integer pageSize = reqParams.getInteger("pageSize");

        JsonArray csChannelArray = reqParams.getJsonArray("csChannelList");
        Future<List<RecentContactFans>> usrLimitFut = Future.future();
        // 获取客服下在线粉丝
        sqlService.getChannelOnlineFansByCSChannelList(csChannelArray, name, pageNum, pageSize)
                .setHandler(usrLimitFut.completer());
        Future<Void> csOnlineUserFuture = makeUserOnlineInfo(usrLimitFut, response);
        csOnlineUserFuture.setHandler(csOnlineAr -> {
            if (csOnlineAr.failed()) {
                LOGGER.info("[获取客服下所有在线用户失败],error={}", csOnlineAr.cause().getMessage());
            } else {
                LOGGER.info("[获取客服下所有在线用户成功]");
            }
        });
    }

    /**
     * 统计客服下所有在线用户数量
     * @param context
     */
    @RouteMapping(path = "/:csid/online/countall")
    @MethodLog("客服下在线粉丝总数统计")
    public void countAllOnlineUserInCustomerServer(RoutingContext context) {
        HttpServerRequest request = context.request();
        HttpServerResponse response = context.response();

        String csId = request.getParam("csid");
        Integer currentIndex = 0;
        Integer pageSize = -1;

        Future<List<RecentContactFans>> countUsrOnlineInCSFut = Future.future();
        // 获取客服下在线粉丝
        sqlService.getUserLimitByCsId(csId, currentIndex, pageSize)
                .compose(userLimits -> {
                    JsonObject countResult = new JsonObject();
                    Integer count = 0;
                    for (UserLimit limit : userLimits) {
                        if (limit.getTextLimit() == 0) {
                            // Web粉丝不在线
                            continue;
                        }
                        if (limit.getTextLimit() == 100) {
                            // Web在线
                            count ++;
                            continue;
                        }
                        Long currentTimestamp = PeriodicTime.getCurrentTimestamp();
                        Long distance = currentTimestamp - limit.getLaseSendTimestamp();
                        if (distance < ActiveLimit.ACTIVE_LIMIT.getLimit()) {
                            count ++;
                        }
                    }
                    countResult.put("count", count);
                    HttpUtils.commonJsonResponse(response, countResult);
                    countUsrOnlineInCSFut.complete();
                }, countUsrOnlineInCSFut);
        countUsrOnlineInCSFut.setHandler(ar -> {
            if (ar.failed()) {
                LOGGER.info("[统计客服下所有在线用户数量失败]error={}", ar.cause().getMessage());
                HttpUtils.commonJsonResponse(response, Constants.failedResult(ar.cause().getMessage()));
            } else {
                LOGGER.info("[统计客服下所有在线用户数量成功]");
            }
        });
    }

    /**
     * 组装在线用户数据
     * @param recentFansFut
     * @param response
     * @return
     */
    public Future<Void> makeUserOnlineInfo(Future<List<RecentContactFans>> recentFansFut, HttpServerResponse response) {
        Future<Void> responseFut = Future.future();
        recentFansFut
                .compose(fansInfoList -> {
                    JsonArray userLimitArray = new JsonArray();
                    Future<Void> pubFut = Future.future();
                    if (CodeUtil.collectionNotEmpty(fansInfoList)) {
                        dataAccess.getWXPublicInfoByDeveloperId(fansInfoList.get(0).getDeveloperId())
                                .compose(wxPublic -> {
                                    for (RecentContactFans fans : fansInfoList) {
                                        if (fans.getLimitText()!= null && fans.getLimitText() == 0) {
                                            continue;
                                        }
                                        Long restHours = 24L;
                                        Long currentTimestamp = PeriodicTime.getCurrentTimestamp();
                                        Long distance = currentTimestamp - fans.getLastActTimestamp();
                                        if (fans.getLimitText() == 100) {
                                            // web粉丝在线时时间显示为24:00
                                            restHours = 24L;
                                        } else if (distance < ActiveLimit.ACTIVE_LIMIT.getLimit()) {
                                            restHours = (ActiveLimit.ACTIVE_LIMIT.getLimit() - distance) / 3600;
                                        } else {
                                            continue;
                                        }
                                        JsonObject ulJson = new JsonObject()
                                                .put("openid", fans.getOpenId())
                                                .put("pubname", wxPublic.getName())
                                                .put("resthours", restHours)
                                                .put("content", fans.getContent())
                                                .put("lastsendtime", fans.getLastSendTime()==null?0L:fans.getLastSendTime())
                                                .put("developerid", fans.getDeveloperId());
                                        userLimitArray.add(ulJson);
                                    }
                                    pubFut.complete();
                                }, pubFut);
                    } else {
                        pubFut.complete();
                    }
                    pubFut.compose((Void) -> {
                        response
                                .putHeader("content-type", "text/json")
                                .end(userLimitArray.encodePrettily());
                        responseFut.complete();
                    }, responseFut);
                }, responseFut);
        return responseFut;
    }

    /**
     * 获取指定客服服务排行数据
     * @param context
     */
    @RouteMapping(path = "/:developerid/csrank/:pagenum/:size")
    @MethodLog("客服服务排行数据")
    public void getCustomerServiceRank(RoutingContext context) {
        HttpServerRequest request = context.request();
        HttpServerResponse response = context.response();

        String developerId = request.getParam("developerid");
        Integer pageNum = Integer.valueOf(request.getParam("pagenum"));
        Integer pageSize = Integer.valueOf(request.getParam("size"));
        Integer currentIndex = (pageNum -1) * pageSize;

        Future<Void> csServiceRankInfoFut = Future.future();
        // 获取客服下粉丝统计
        sqlService.countCusServDataByDeveloperIdOrCsIdList(developerId, null, currentIndex, pageSize)
                .compose(cusServiceDataList -> {
                    JsonArray returnArray = new JsonArray();
                    for (CusServiceData csd : cusServiceDataList) {
                        JsonObject csJson = new JsonObject()
                                .put("count", csd.getActiveCount())
                                .put("csid", csd.getCsId());
                        returnArray.add(csJson);
                    }
                    response
                            .putHeader("content-type", "text/json")
                            .end(returnArray.encodePrettily());
                    csServiceRankInfoFut.complete();
                }, csServiceRankInfoFut);
        csServiceRankInfoFut.setHandler(csServAr -> {
            if (csServAr.failed()) {
                LOGGER.info("[获取指定客服服务排行数据],error={}", csServAr.cause().getMessage());
            } else {
                LOGGER.info("[获取指定客服服务排行数据成功]");
            }
        });
    }

    /**
     * 获取指定客服列表中的客服服务排行数据
     * @param context
     */
    @RouteMapping(path = "/cusserlist/csrank", method = HttpMethod.POST)
    @MethodLog("获取指定客服列表中的客服服务排行数据")
    public void getCustomerServiceRankbyCSList(RoutingContext context) {
        HttpServerRequest request = context.request();
        HttpServerResponse response = context.response();

        JsonObject reqParamJson = context.getBodyAsJson();
        JsonArray csIdList = reqParamJson.getJsonArray("csIdList");
        Integer pageNum = Integer.valueOf(reqParamJson.getInteger("pagenum"));
        Integer pageSize = Integer.valueOf(reqParamJson.getInteger("size"));
        Integer currentIndex = (pageNum -1) * pageSize;

        Future<Void> csServiceRankInfoFut = Future.future();
        // 获取客服下粉丝统计
        sqlService.countCusServDataByDeveloperIdOrCsIdList(null, csIdList, currentIndex, pageSize)
                .compose(cusServiceDataList -> {
                    JsonArray returnArray = new JsonArray();
                    for (CusServiceData csd : cusServiceDataList) {
                        JsonObject csJson = new JsonObject()
                                .put("count", csd.getActiveCount())
                                .put("csid", csd.getCsId());
                        returnArray.add(csJson);
                    }
                    response
                            .putHeader("content-type", "text/json")
                            .end(returnArray.encodePrettily());
                    csServiceRankInfoFut.complete();
                }, csServiceRankInfoFut);
        csServiceRankInfoFut.setHandler(csServAr -> {
            if (csServAr.failed()) {
                LOGGER.info("[获取指定客服服务排行数据],error={}", csServAr.cause().getMessage());
            } else {
                LOGGER.info("[获取指定客服服务排行数据成功]");
            }
        });
    }

    /**
     * 获取指定列表中的粉丝的最近一条聊天信息
     * @param context
     */
    @RouteMapping(path = "/list/lastmsgrecord", method = HttpMethod.POST)
    @MethodLog("返回指定粉丝的最后的聊天记录信息")
    public void getLatelyMsgRecordInList(RoutingContext context) {
        HttpServerRequest request = context.request();
        HttpServerResponse response = context.response();

        JsonArray openIdList = context.getBodyAsJsonArray();
        if(!CodeUtil.collectionNotEmpty(openIdList)) {
            throw new IllegalArgumentException("[getLatelyMsgRecordInList]->{openIdList}为空");
        }
        Future<Void> latelyMsgFut = Future.future();
        sqlService.getLatelyMsgRecordByOpenId(openIdList)
                .compose(msgList ->{
                    JsonArray respJson = new JsonArray();
                    for (MsgRecord msg : msgList) {
                        msg.decodeContent();
                        JsonObject msgJson = new JsonObject();
                        if (MsgType.TEXT.getType().equals(msg.getMsgType())) {
                            msgJson.put("content", new JsonObject(msg.getContent()).getString("content"));
                        } else if (MsgType.IMAGE.getType().equals(msg.getMsgType())) {
                            msgJson.put("content", "[图片]");
                        }
                        msgJson.put("openid", msg.getOpenId())
                                .put("sendtime", msg.getSendtime());
                        respJson.add(msgJson);
                    }
                    HttpUtils.commonJsonResponse(response, respJson);
                    latelyMsgFut.complete();
                }, latelyMsgFut);
        latelyMsgFut.setHandler(ar -> {
            if (ar.failed()) {
                LOGGER.info("[获取指定列表中的粉丝的最近一条聊天信息失败]:error={}", ar.cause().getMessage());
            } else {
                LOGGER.info("[获取指定列表中的粉丝的最近一条聊天信息成功]");
            }
        });
    }

    /**
     * 模糊查询在线粉丝
     * @param context
     */
    @RouteMapping(path = "/fans/online/:developerid/:name/:pagenum/:size")
    @MethodLog("公众号下在线粉丝模糊查询")
    public void getOnlineFansByName(RoutingContext context) {
        HttpServerRequest request = context.request();
        HttpServerResponse response = context.response();
        String name = request.getParam("name");
        String developerId = request.getParam("developerid");
        PageInfo page = new PageInfo(request);
        Objects.requireNonNull(name);
        Future<Void> fansFut = Future.future();
        sqlService.getOnlineFansByName(developerId, name, page)
                .compose(recentContactFansList -> {
                    JsonArray recentContactArray = new JsonArray();
                    for (RecentContactFans fans : recentContactFansList) {
                        if (fans.getLimitText() != null && fans.getLimitText() == 0) {
                            continue;
                        }
                        if (fans.getLimitText() == 100) {
                            // Web在线粉丝直接显示24小时后离线
                            Long restHours = 24L;
                            JsonObject ulJson = new JsonObject()
                                    .put("openid", fans.getOpenId())
                                    .put("resthours", restHours)
                                    .put("content", EmojiUtil.decodeEmoji(new JsonObject(fans.getContent()).getString("content")))
                                    .put("lastsendtime", fans.getLastSendTime());
                            recentContactArray.add(ulJson);
                        }
                        Long currentTimestamp = PeriodicTime.getCurrentTimestamp();
                        Long distance = currentTimestamp - fans.getLastActTimestamp();
                        if (distance < ActiveLimit.ACTIVE_LIMIT.getLimit()) {
                            Long restHours = (ActiveLimit.ACTIVE_LIMIT.getLimit() - distance) / 3600;
                            JsonObject ulJson = new JsonObject()
                                    .put("openid", fans.getOpenId())
                                    .put("resthours", restHours)
                                    .put("content", EmojiUtil.decodeEmoji(new JsonObject(fans.getContent()).getString("content")))
                                    .put("lastsendtime", fans.getLastSendTime());
                            recentContactArray.add(ulJson);
                        }
                    }
                    HttpUtils.commonJsonResponse(response, recentContactArray);
                    fansFut.complete();
                }, fansFut);

        // 通用future结束处理
        commonEndFuture(fansFut, "模糊查询在线粉丝", LOGGER);
    }

    /**
     * 公众号下最近联系粉丝
     * @param context
     */
    @RouteMapping(path = "/pub/:developerid/fans/lately/:pagenum/:size")
    @MethodLog("公众号下最近联系的粉丝")
    public void getRecentContactFansByDeveloperId(RoutingContext context) {
        HttpServerRequest request = context.request();
        HttpServerResponse response = context.response();

        String developerId = request.getParam("developerid");
        PageInfo page = new PageInfo(request);
        Future<Void> fansFut = Future.future();
        sqlService.getRecentContactFans(null, developerId, null, page)
                .compose(recentContactFansList -> {
                    JsonArray recentContactArray = getrecentContactArrayByFansList(recentContactFansList);
                    HttpUtils.commonJsonResponse(response, recentContactArray);
                    fansFut.complete();
                }, fansFut);

        // 通用future结束处理
        commonEndFuture(fansFut, "公众号下最近联系粉丝", LOGGER);
    }

    /**
     * 【代码块，已删除】
     */
    /**
     * 客服下最近联系粉丝
     * @param context
     */
    @RouteMapping(path = "/:csid/fans/lately/:pagenum/:size")
    @MethodLog("客服下最近联系粉丝")
    public void getRecentContactFansInCS(RoutingContext context) {
        HttpServerRequest request = context.request();
        HttpServerResponse response = context.response();

        String csId = request.getParam("csid");

        PageInfo page = new PageInfo(request);
        Future<Void> fansFut = Future.future();
        sqlService.getRecentContactFans(csId, null, null, page)
                .compose(recentContactFansList -> {
                    JsonArray recentContactArray = recentContactFans2Json(recentContactFansList);
                    HttpUtils.commonJsonResponse(response, recentContactArray);
                    fansFut.complete();
                }, fansFut);

        // 通用future结束处理
        commonEndFuture(fansFut, "客服下最近联系粉丝", LOGGER);
    }

    /**
     * 模糊查询客服下最近联系粉丝
     * @param context
     */
    @RouteMapping(path = "/:csid/fans/:name/lately/:pagenum/:size")
    @MethodLog("模糊查询客服下最近联系粉丝")
    public void fuzzyQueryRecentContactFansInCS(RoutingContext context) {
        HttpServerRequest request = context.request();
        HttpServerResponse response = context.response();

        String csId = request.getParam("csid");
        String name = request.getParam("name");
        PageInfo page = new PageInfo(request);
        Future<Void> fansFut = Future.future();
        sqlService.getRecentContactFans(csId, null, name, page)
                .compose(recentContactFansList -> {
                    JsonArray recentContactArray = recentContactFans2Json(recentContactFansList);
                    HttpUtils.commonJsonResponse(response, recentContactArray);
                    fansFut.complete();
                }, fansFut);

        // 通用future结束处理
        commonEndFuture(fansFut, "模糊查询客服下最近联系粉丝", LOGGER);
    }

    /**
     * 昵称模糊查询公众号下最近联系粉丝
     * @param context
     */
    @RouteMapping(path = "/pub/:developerid/fans/lately/:name/:pagenum/:size")
    @MethodLog("公众号下最近联系的粉丝名字模糊查询")
    public void getRecentContactFansByName(RoutingContext context) {
        HttpServerRequest request = context.request();
        HttpServerResponse response = context.response();

        String developerId = request.getParam("developerid");
        String name = request.getParam("name");
        PageInfo page = new PageInfo(request);
        Future<Void> fansFut = Future.future();
        sqlService.getRecentContactFans(null, developerId, name, page)
                .compose(recentContactFansList -> {
                    JsonArray recentContactArray = getrecentContactArrayByFansList(recentContactFansList);
                    HttpUtils.commonJsonResponse(response, recentContactArray);
                    fansFut.complete();
                }, fansFut);

        // 通用future结束处理
        commonEndFuture(fansFut, "昵称模糊查询公众号下最近联系粉丝", LOGGER);
    }

    @RouteMapping(path = "/uid/:token")
    @MethodLog("根据access_token获取微博uid")
    public void getWBUidByToken (RoutingContext context) {
        String token = context.request().getParam("token");
        if (StringUtils.isNotBlank(token)) {
            webClientService.getUidFromAccessToken(token)
                    .setHandler(ar -> {
                        if (ar.succeeded()) {
                            JsonObject uidJson = new JsonObject().put("uid", ar.result());
                            HttpUtils.commonJsonResponse(context.response(), uidJson);
                        } else {
                            LOGGER.info("get uid from token failed: {}", ar.cause().getMessage());
                            HttpUtils.commonJsonResponse(context.response(), SendResult.FAILED_HANDLE.getResultJson(ar.cause().getMessage()));
                        }
                    });
        } else {
            HttpUtils.commonJsonResponse(context.response(), SendResult.FAILED_PARAM.getResultJson("token"));
        }
    }

    @RouteMapping(path = "/blacklist", method = HttpMethod.POST)
    @MethodLog("拉黑/取消拉黑粉丝操作")
    public void wxBlackListOperation(RoutingContext context) {
        JsonObject reqJson = context.getBodyAsJson();

        HttpServerResponse resp = context.response();
        if (reqJson == null || reqJson.getJsonArray("channelUserIdList") == null || reqJson.getJsonArray("channelUserIdList").size() == 0) {
            HttpUtils.commonJsonResponse(resp, Constants.failedResult("粉丝列表为空"));
        }
        JsonArray openIdArray = reqJson.getJsonArray("channelUserIdList");
        String channelReceiverId = reqJson.getString("channelReceiverId");
        if (StringUtils.isEmpty(channelReceiverId)) {
            HttpUtils.commonJsonResponse(resp, Constants.failedResult("渠道信息为空"));
        }
        Integer operation = reqJson.getInteger("operation"); // 0: 拉黑粉丝；1：取消拉黑粉丝
        Future<Void> operationFut = Future.future();
        dataAccess.getWXPublicInfoByDeveloperId(channelReceiverId)
                .compose(wxPublic -> {
                    String accessToken = wxPublic.getAccessToken();
                    if (ChannelType.WX.val() == wxPublic.getChannelType()) {
                        if (0 == operation) {
                            webClientService.pullBlackByList(accessToken, (List<String>)openIdArray.getList())
                                    .setHandler(operationFut.completer());
                        } else if (1 == operation) {
                            webClientService.canclePullBackByList(accessToken, (List<String>)openIdArray.getList())
                                    .setHandler(operationFut.completer());
                        }
                    } else if (ChannelType.WB.val() == wxPublic.getChannelType()) {

                        dataAccess.setPullBlackStatusForWB((List<String>)openIdArray.getList(), 0==operation)
                                .setHandler(operationFut.completer());
                    } else if (ChannelType.WEB.val() == wxPublic.getChannelType()) {
                        // web粉丝拉黑跟微博的处理一样
                        dataAccess.setPullBlackStatusForWB((List<String>)openIdArray.getList(), 0==operation)
                                .setHandler(operationFut.completer());
                    } else {
                        HttpUtils.commonJsonResponse(resp, Constants.failedResult("操作类型不正确: " + operation));
                        operationFut.fail("操作类型不正确 operation={}" + operation);
                    }
                }, operationFut);

        operationFut.setHandler(ar -> {
            if (ar.failed()) {
                // 粉丝拉黑失败
                LOGGER.error("拉黑粉丝处理失败: errmsg={}\n resquest={}", ar.cause().getMessage(), reqJson.encodePrettily());
                HttpUtils.commonJsonResponse(resp, SendResult.FAILED_HANDLE.getResultJson(ar.cause().getMessage()));
            } else {
                // 粉丝拉黑成功
                HttpUtils.commonJsonResponse(resp, SendResult.SUCCESS.getResultJson(null));
            }
        });

    }

    @RouteMapping(path = "/wx/token")
    @MethodLog("app后台获取accessToken")
    public void appGetAccessToken(RoutingContext context) {
        HttpServerRequest request = context.request();
        HttpServerResponse response = context.response();

        String developerId = request.getParam("developerId");
        getAccessTokenIfExpired(developerId)
                .setHandler(tokenAr -> {
                    if (tokenAr.failed()) {
                        HttpUtils.commonJsonResponse(response,
                                SendResult.FAILED_HANDLE.getResultJson(tokenAr.cause().getMessage()));
                        LOGGER.info("app后台获取accessToken失败：{}", tokenAr.cause().getMessage());
                    } else {
                        JsonObject tokenStr = new JsonObject()
                                .put("accessToken", tokenAr.result());
                        response.end(tokenStr.encode());
                    }
                });

    }

    @RouteMapping(path = "/check/user/online")
    @MethodLog("app后台验证用户是否在线并返回在线时长")
    public void checkUserOnline(RoutingContext context) {
        HttpServerRequest request = context.request();
        HttpServerResponse response = context.response();
        String openId = request.getParam("openId");
        if (StringUtils.isBlank(openId)) {
            HttpUtils.commonJsonResponse(response,
                    SendResult.FAILED_PARAM.getResultJson("openId为空"));
        }
        sqlService.getUserLimitByOpenId(openId)
                .setHandler(ulAr -> {
                   if (ulAr.succeeded()) {
                       JsonObject respJson = new JsonObject();
                       long distance = 0;
                       if (ulAr.result() != null) {
                           if (ulAr.result().getTextLimit() > 0) {
                               if (ulAr.result().getTextLimit() == 100) {
                                   // Web粉丝。只要在线就显示在线时间24小时
                                   distance = 24;
                               } else {
                                   distance = WxUtils.checkUserOnline(ulAr.result().getLaseSendTimestamp());
                               }
                           }
                       }
                       respJson.put("online", distance>0)
                               .put("restHour", distance);
                       response.end(respJson.encode());
                   } else {
                       HttpUtils.commonJsonResponse(response,
                               SendResult.FAILED_HANDLE.getResultJson(ulAr.cause().getMessage()));
                   }
                });
    }

    public JsonArray getrecentContactArrayByFansList(List<RecentContactFans> recentContactFansList) {
        JsonArray recentContactArray = new JsonArray();
//        for (RecentContactFans fans : recentContactFansList) {
//            Long currentTimestamp = PeriodicTime.getCurrentTimestamp();
//            Long distance = currentTimestamp - fans.getLastActTimestamp();
//            if (distance < ActiveLimit.ACTIVE_LIMIT.getLimit()) {
//                Long restHours = (ActiveLimit.ACTIVE_LIMIT.getLimit() - distance) / 3600;
//                JsonObject ulJson = new JsonObject()
//                        .put("openid", fans.getOpenId())
//                        // .put("resthours", restHours)
//                        .put("content", EmojiUtil.decodeEmoji(new JsonObject(fans.getContent()).getString("content")))
//                        .put("sendtime", fans.getLastSendTime());
//                recentContactArray.add(ulJson);
//            }
//        }

        for (RecentContactFans fans : recentContactFansList) {
            JsonObject ulJson = new JsonObject()
                    .put("openid", fans.getOpenId())
                    // .put("resthours", restHours)
                    .put("content", EmojiUtil.decodeEmoji(new JsonObject(fans.getContent()).getString("content")))
                    .put("sendtime", fans.getLastSendTime());
            recentContactArray.add(ulJson);
        }
        return recentContactArray;
    }

    /**
     * 组装最近联系粉丝的最近聊天
     * @param recentContactFansList
     * @return
     */
    public JsonArray recentContactFans2Json (List<RecentContactFans> recentContactFansList) {
        JsonArray recentContactArray = new JsonArray();
        for (RecentContactFans fans : recentContactFansList) {
            JsonObject ulJson = new JsonObject()
                    .put("openid", fans.getOpenId())
                    // .put("resthours", restHours)
                    .put("content", EmojiUtil.decodeEmoji(new JsonObject(fans.getContent()).getString("content")))
                    .put("sendtime", fans.getLastSendTime());
            recentContactArray.add(ulJson);
        }
        return recentContactArray;
    }

}
